﻿//Copyright 2011 - Jonathan Allen

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Granite.Modeling.Internals;
using Granite.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Runtime.Serialization;

namespace Granite.Modeling
{
	/// <summary>
	/// This ModelBase adds the IEditableObject interface to the ChangeTrackingModelBase. The IEditableObject methods are designed for dialogs with ok/cancel semantics. Use the IChangeTracking to track unsaved changes.
	/// </summary>
	public class EditableObjectModelBase : ModelBase<EditableObjectPropertyBag>, IRevertibleChangeTracking, IChangeTrackingGraph, IEditableObject
	{
		bool m_IsChanged;
		bool m_IsChangedOld;

		/// <summary>
		/// 
		/// </summary>
		public EditableObjectModelBase()
		{
			Properties.PropertyChanged += (s, e) => IsChanged = true;
		}

		/// <summary>
		/// Begins an edit on an object.
		/// </summary>
		public virtual void BeginEdit()
		{
			Contract.Ensures(Properties.IsEditing == true);

			if (Properties.IsEditing)
				return;

			Properties.BeginEdit();
			m_IsChangedOld = m_IsChanged;
			Contract.Assume(Properties.IsEditing == true);
		}

		/// <summary>
		/// Discards changes since the last <see cref="M:System.ComponentModel.IEditableObject.BeginEdit" /> call.
		/// </summary>
		public virtual void CancelEdit()
		{
			Contract.Ensures(Properties.IsEditing == false);

			if (!Properties.IsEditing)
				return;

			Properties.CancelEdit();
			m_IsChanged = m_IsChangedOld; //restore old IsChanged flag
			Contract.Assume(Properties.IsEditing == false);
		}

		/// <summary>
		/// Pushes changes since the last <see cref="M:System.ComponentModel.IEditableObject.BeginEdit" /> or <see cref="M:System.ComponentModel.IBindingList.AddNew" /> call into the underlying object.
		/// </summary>
		public virtual void EndEdit()
		{
			Contract.Ensures(Properties.IsEditing == false);

			if (!Properties.IsEditing)
				return;

			Properties.EndEdit();
		}

		/// <summary>
		/// Returns True if any fields were modified since the last call to AcceptChanges. This does not walk the object graph.
		/// </summary>
		public bool IsChanged
		{
			get { return m_IsChanged; }
			protected set
			{
				if (m_IsChanged == value)
					return;
				m_IsChanged = value;
				InvokePropertyChanged(CommonProperties.IsChangedProperty);
			}
		}

		/// <summary>
		/// Walk the object graph, looking for changed items.
		/// </summary>
		/// <returns></returns>
		[Pure]
		public bool IsChangedGraph()
		{
			if (IsChanged)
				return true;
			return Properties.IsChangedGraph();
		}

		/// <summary>
		/// Marks all fields as unchanged and clears the IsChanged flag.
		/// </summary>
		/// <remarks>This will call AcceptChanges on properties that implement IChangeTracking</remarks>
		public void AcceptChanges()
		{
			Contract.Ensures(Properties.IsEditing == false);
			
			Properties.AcceptChanges(true);
			IsChanged = false;

			Contract.Assume(Properties.IsEditing == false);
		}

		/// <summary>
		/// Discards all pending changes and reverts to the values used the last time AcceptChanges was called.
		/// </summary>
		/// <remarks>This will call RejectChanges on properties that implement IRevertibleChangeTracking</remarks>
		public void RejectChanges()
		{
			Contract.Ensures(Properties.IsEditing == false);

			Properties.RejectChanges(true);
			IsChanged = false;

			Contract.Assume(Properties.IsEditing == false);
		}

		//      /// <summary>
		//      /// Updates the model using the dictionary and accepts the changes. 
		//      /// </summary>
		//      /// <param name="fields"></param>
		///// <param name="updateMode"></param>
		//      /// <remarks>This is usually called as a result of a stored procedure call. Said stored procedure only needs to return altered fields.</remarks>
		//      public void Update(IDictionary<string, object> fields, UpdateMode updateMode)
		//      {
		//          if (fields == null)
		//              throw new ArgumentNullException("fields", "fields is null.");

		//          Load(fields);

		//          if (updateMode == UpdateMode.AcceptChanges)
		//              AcceptChanges();
		//          else if (updateMode == UpdateMode.AcceptLocalChanges)
		//              Properties.AcceptChanges(false);
		//      }

		[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode"), SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
		[ContractInvariantMethod]
		void ObjectInvariant()
		{

		}

		[OnDeserialized]
		void OnDeserialized(StreamingContext context)
		{
			AcceptChanges();
		}
	}
}
